/* Copyright 2013 Tobias Marschall
 *
 * This file is part of CLEVER.
 *
 * CLEVER is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CLEVER is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CLEVER.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <ctime>

#include <boost/program_options.hpp>

#include <bamtools/api/BamWriter.h>

#include "GroupWiseBamReader.h"
#include "BamHelper.h"
#include "VersionInfo.h"

using namespace std;
namespace po = boost::program_options;

void usage(const char* name, const po::options_description& options_desc) {
	cerr << "Usage: " << name << " [options]" << endl;
	cerr << endl;
	cerr << "Reads a BAM from stdin converts alternative alignments in multiline" << endl;
	cerr << "format to XA tags and writes resulting BAM to stdout." << endl;
	cerr << endl;
	cerr << options_desc << endl;
	exit(1);
}

void write_alignments(BamTools::BamWriter& bam_writer, const BamTools::RefVector& bam_ref_data, const vector<BamTools::BamAlignment*>& alignments) {
	if (alignments.size() == 0) return;
	if (alignments.size() == 1) {
		bam_writer.SaveAlignment(*(alignments[0]));
		return;
	}
	int primary = 0;
	for (size_t i = 0; i < alignments.size(); ++i) {
		if (alignments[i]->IsPrimaryAlignment()) {
			primary = i;
			break;
		}
	}
	BamTools::BamAlignment primary_aln(*(alignments[primary]));
	uint32_t x = 0;
	string s = "";
	if (primary_aln.GetTag("X0", x)) primary_aln.RemoveTag("X0");
	if (primary_aln.GetTag("X1", x)) primary_aln.RemoveTag("X1");
	if (primary_aln.GetTag("XA", s)) primary_aln.RemoveTag("XA");
	if (!primary_aln.AddTag("X0", "i", (int32_t)1)) assert(false);
	if (!primary_aln.AddTag("X1", "i", (int32_t)(alignments.size() - 1))) assert(false);
	ostringstream xa;
	for (size_t i = 0; i < alignments.size(); ++i) {
		if (i == primary) continue;
		const BamTools::BamAlignment& aln = *(alignments[i]);
		if (!aln.IsMapped()) continue;
		uint32_t nm = 0;
		if (!aln.GetTag("NM", nm)) nm = 0;
		xa << bam_ref_data[aln.RefID].RefName << ',' << (aln.IsReverseStrand()?'-':'+') << (aln.Position + 1) << ',' << aln.CigarData << ',' << nm << ';';
	}
	if (!primary_aln.AddTag("XA", "Z", xa.str())) assert(false);
	bam_writer.SaveAlignment(primary_aln);
}

int main(int argc, char* argv[]) {
	VersionInfo::checkAndPrintVersion("multiline-to-xa", cerr);
	string commandline = VersionInfo::commandline(argc, argv);

	// PARAMETERS
	bool omit_alt_cigars = false;
	bool omit_secondary_aln = false;
	string snp_filename = "";
	int phred_offset;
	bool simple_cigar;
	string variations_filename = "";

	po::options_description options_desc("Allowed options");
// 	options_desc.add_options()
// 	;
	
	if (isatty(fileno(stdin))) {
		usage(argv[0], options_desc);
	}

	po::variables_map options;
	try {
		po::store(po::parse_command_line(argc, argv, options_desc), options);
		po::notify(options);
	} catch(exception& e) {
		cerr << "error: " << e.what() << "\n";
		return 1;
	}
	cerr << "Commandline: " << commandline << endl;

	clock_t clock_start = clock();
	try {
		// open input BAM file
		GroupWiseBamReader bam_reader("/dev/stdin", true, false);
		const BamTools::RefVector& bam_ref_data = bam_reader.getReferenceData();
		const BamTools::SamHeader& sam_header = bam_reader.getHeader();
		bam_reader.enableProgressMessages(cerr, 200000);
		// create output BAM file
		BamTools::BamWriter bam_writer;
		if (!bam_writer.Open("/dev/stdout", sam_header, bam_ref_data)) {
			cerr << "Error writing BAM to stdout." << endl;
			return 1;
		}
		while ( bam_reader.hasNext() ) {
			bam_reader.advance();
			write_alignments(bam_writer, bam_ref_data, bam_reader.getAlignmentsFirst());
			write_alignments(bam_writer, bam_ref_data, bam_reader.getAlignmentsSecond());
		}
		bam_writer.Close();
	} catch(exception& e) {
		cerr << "Error: " << e.what() << "\n";
		return 1;
	}
	double cpu_time = (double)(clock() - clock_start) / CLOCKS_PER_SEC;
	cerr << "Total CPU time: " << cpu_time << endl;
	return 0;
}
